package org.sunrise.game.client.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import lombok.Getter;
import lombok.Setter;
import org.sunrise.game.common.log.LogCore;
import org.sunrise.game.common.network.coder.WebSocketMessageCodec;
import org.sunrise.game.common.network.message.MessageType;
import org.sunrise.game.common.network.message.SocketMessage;
import org.sunrise.game.common.utils.Utils;

import java.net.URI;
import java.nio.charset.StandardCharsets;

@Getter
@Setter
public class WsClient extends SocketClient {
    public static final WsClient instance = new WsClient();

    public void connect(String host, int port) {
        EventLoopGroup group = Utils.isLinux() ? new EpollEventLoopGroup(1) : new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(Utils.isLinux() ? EpollSocketChannel.class : NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new HttpClientCodec());
                            ch.pipeline().addLast(new HttpObjectAggregator(65536));
                            ch.pipeline().addLast(WebSocketClientCompressionHandler.INSTANCE);
                            ch.pipeline().addLast(new WebSocketMessageCodec());
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<Object>() {
                                private final WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
                                        URI.create("ws://" + host + ":" + port),
                                        WebSocketVersion.V13,
                                        null,
                                        true,
                                        HttpHeaders.EMPTY_HEADERS,
                                        128 * 1024 * 1024
                                );

                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    LogCore.Client.info("Starting WebSocket handshake...");
                                    handshaker.handshake(ctx.channel());
                                }

                                @Override
                                protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    if (!handshaker.isHandshakeComplete()) {
                                        handleHandshake(ctx, msg);
                                    } else if (msg instanceof SocketMessage socketMessage) {
                                        handleSocketMessage(ctx, socketMessage);
                                    } else {
                                        LogCore.Client.warn("Received unexpected message: {}", msg);
                                    }
                                }

                                private void handleHandshake(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    if (msg instanceof FullHttpResponse response) {
                                        handshaker.finishHandshake(ctx.channel(), response);
                                        LogCore.Client.info("WebSocket handshake complete");
                                        sendInitialMessage(ctx);
                                        connectSuccess = true;
                                    } else {
                                        LogCore.Client.error("Unexpected handshake response: {}", msg);
                                        ctx.close();
                                    }
                                }

                                private void sendInitialMessage(ChannelHandlerContext ctx) {
                                    String connectMessage = Utils.CLIENT_CONNECT + getConnectId();
                                    SocketMessage initialMessage = new SocketMessage(MessageType.biz, connectMessage.getBytes(StandardCharsets.UTF_8));
                                    ctx.writeAndFlush(initialMessage);
                                    LogCore.Client.info("Sent initial message: {}", connectMessage);
                                }

                                private void handleSocketMessage(ChannelHandlerContext ctx, SocketMessage message) {
                                    byte[] data = message.getData();
                                    LogCore.Client.debug("Received message: {}", new String(data, StandardCharsets.UTF_8));
                                    ClientMessageHandler.handler(WsClient.this, ctx, data);
                                }

                                @Override
                                public void channelInactive(ChannelHandlerContext ctx) {
                                    LogCore.Client.warn("WebSocket connection closed");
                                    group.shutdownGracefully();
                                }

                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                                    LogCore.Client.error("Exception caught in WebSocket client", cause);
                                    ctx.close();
                                }
                            });
                        }
                    });

            bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    this.channel = future.channel();
                    LogCore.Client.info("WebSocket connected to {}:{}", host, port);
                } else {
                    LogCore.Client.error("Failed to connect WebSocket to {}:{}", host, port);
                    group.shutdownGracefully();
                }
            });
        } catch (Exception e) {
            LogCore.Client.error("WebSocket connection failed", e);
            group.shutdownGracefully();
        }
    }
}